﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Microsoft.Practices.Composite.Presentation.Events;
using Might.GraphicsEditor.GraphicsObjects;
using Might.Infrastructure.Annotations.Graphics;
using Might.Infrastructure.Events;
using Might.Infrastructure.Services;

namespace Might.GraphicsEditor.Presenter {
    /// <summary>
    /// Class responsible for graphic editing of Icons and Models
    /// </summary>
    public partial class IconPresenter {
        internal IGraphicsAnnotation ga;
        internal Canvas canvas;
        internal List<GraphicsComponent> SelectedShapes;
        private double oldWidth, oldHeight;
        private const int DEFAULT_SIZE = 200;
        
        public event EventHandler SelectionChanged;
        public event EventHandler SelectionMoved;

        /// <summary>
        /// Public default constructor for class DrawCanvas.
        /// </summary>
        /// <param name="ga">Graphic Annotation to be used in this canvas.</param>
        public IconPresenter(IGraphicsAnnotation ga) {
            InitializeComponent();
            this.ga = ga;
            
            // initialize all layers of annotation
            for (int i = ga.InheritedGraphics.Count - 1; i >= 0; i--) {
                // initialize each layer
                IconPresenter t = new IconPresenter(ga.InheritedGraphics[i]);
                topGrid.Children.Add(t);
            }

            canvas = CreateTopLayer(ga.Graphics);
            canvas.Width = canvas.Height = DEFAULT_SIZE;
            topGrid.Children.Add(canvas);
            SelectedShapes = new List<GraphicsComponent>();

            Width = Height = oldWidth = oldHeight = DEFAULT_SIZE;
            IconScale.ScaleX = IconScale.ScaleY = 1;

            SizeChanged += IconPresenter_SizeChanged;
            ga.PropertyChanged += ga_PropertyChanged;

            CoreServices.EventAggregator.GetEvent<GraphicsPrimitiveSelectedEvent>().Subscribe(
                GraphicsPrimitiveSelectedEventHandler, ThreadOption.UIThread, false);
        }

        /// <param name="prim">The graphics primitive</param>
        public void GraphicsPrimitiveSelectedEventHandler(IGraphicsItem prim) {
            GraphicsComponent x = (GraphicsComponent)canvas.Children.Where(k => (k is GraphicsComponent) && ((k as GraphicsComponent).ga == prim)).FirstOrDefault();
            if (x != null && !IsSelected(x)) {
                SetShape(x);
            }
        }

        private static Canvas CreateTopLayer(IEnumerable<IGraphicsItem> graphics) {
            Canvas canvas = new Canvas();

            // initialize all object of current layer
            foreach (IGraphicsItem item in graphics) {
                AddGraphicsPrimitive(item, canvas);
            }
            return canvas;
        }

        private static void AddGraphicsPrimitive(IGraphicsItem gp, Panel c) {
            if (gp is IGraphicsEllipse) {
                c.Children.Add(new LeafEllipse(gp));
            } else if (gp is IGraphicsRectangle) {
                c.Children.Add(new LeafRectangle(gp));
            } else if (gp is IGraphicsLine) {
                c.Children.Add(new LeafLine(gp));
            } else if (gp is IGraphicsPolygon) {
                c.Children.Add(new LeafPolygon(gp));
            } else if (gp is IGraphicsBitmap) {
                c.Children.Add(new LeafBitmap(gp));
            } else if (gp is IGraphicsText) {
                c.Children.Add(new LeafText(gp));
            } else {
                throw new Exception("Unknown graphics primitive");
            }
        }

        private void IconPresenter_SizeChanged(object sender, SizeChangedEventArgs e) {
            if (Width < 5) {
                Width = 5; // workaround for 0 width/height
            }
            if (Height < 5) {
                Height = 5;
            }
            double Wratio = 0;
            double Hratio = 0;

            if (Width != oldWidth) {
                oldWidth = Width;
                Wratio = Width / DEFAULT_SIZE;
            }
            if (Height != oldHeight) {
                oldHeight = Height;
                Hratio = Height / DEFAULT_SIZE;
            }

            if (Wratio != 0 || Hratio != 0) {
                IconScale.ScaleX = Wratio;
                IconScale.ScaleY = Hratio;
            }
        }
        
        void ga_PropertyChanged(object sender,PropertyChangedEventArgs e) {
            if (e.PropertyName != "Children") {
                return;
            }

            //ClearSelection();
            var list = new List<UIElement>(canvas.Children);
            canvas.Children.Clear();

            foreach (IGraphicsItem item in ga.Graphics) {
                UIElement find =
                    (list.Where(n => ((GraphicsComponent)n).ga == item)).FirstOrDefault();
                if (find != null) {
                    canvas.Children.Add(find);
                } else {
                    AddGraphicsPrimitive(item, canvas);
                }
            }
        }


        #region Selection manipulation

        #region Single shape manipulation

        internal bool IsSelected(GraphicsComponent item) {
            return SelectedShapes.Contains(item);
        }

        internal void AddShape(GraphicsComponent item) {
            if (item == null) {
                throw new NullReferenceException("Can not add null item");
            }
            if (SelectedShapes.Contains(item)) {
                throw new ArgumentException("item already in selection");
            }

            SelectedShapes.Add(item);
            OnSelectionChanged();
        }

        internal void SetShape(GraphicsComponent item) {
            if (item == null) {
                throw new NullReferenceException("Can not set null item");
            }

            SelectedShapes = new List<GraphicsComponent> { item };
            OnSelectionChanged();
        }

        internal void RemoveShape(GraphicsComponent item) {
            if (item == null) {
                throw new NullReferenceException("Can not remove null item");
            }
            if (!SelectedShapes.Contains(item)) {
                throw new ArgumentException("Can not remove GraphicsComponent from selection");
            }

            SelectedShapes.Remove(item);
            OnSelectionChanged();
        }

        #endregion single Shape manipulation

        #region Multi shape manipulation

        internal void AddSelection(List<GraphicsComponent> list) {
            if (list == null) {
                throw new NullReferenceException("Can not add null item");
            }

            SelectedShapes = SelectedShapes.Union(list).ToList();
            OnSelectionChanged();
        }

        /// <summary>
        /// Main function for modifiing the selection, used by other selection functions.
        /// </summary>
        /// <param name="list"></param>
        internal void SetSelection(List<GraphicsComponent> list) {
            if (list == null) {
                throw new NullReferenceException("Can not set null itemlist");
            }

            SelectedShapes = list;
            OnSelectionChanged();
        }

        internal void RemoveSelection(List<GraphicsComponent> list) {
            if (list == null) {
                throw new NullReferenceException("Can not remove null item list");
            }

            SelectedShapes = SelectedShapes.Except(list).ToList();
            OnSelectionChanged();
        }
        
        internal void ClearSelection() {
            SelectedShapes.Clear();
            OnSelectionChanged();
        }

        #endregion multi Shape manipulation
        
        #endregion Selection manipulation

        #region Move action - simplified transformation

        internal void PreviewMoveSelection(Point delta) {
            for (int i = 0; i < SelectedShapes.Count; i++) {
                if (SelectedShapes[i] != null) {
                    SelectedShapes[i].Left += delta.X;
                    SelectedShapes[i].Top += delta.Y;
                } else {
                    throw new NullReferenceException("Null object in SelectedShapes");
                }
            }

            if (SelectionMoved != null) {
                SelectionMoved(this, null);
            }
        }

        internal void MoveSelection() {
            for (int i = 0; i < SelectedShapes.Count; i++) {
                if (SelectedShapes[i] != null) {
                    SelectedShapes[i].ga.StartUpdate();

                    SelectedShapes[i].ga.Left = SelectedShapes[i].Left;
                    SelectedShapes[i].ga.Top = SelectedShapes[i].Top;

                    SelectedShapes[i].ga.FinishUpdate();
                } else {
                    throw new NullReferenceException("Null object in SelectedShapes");
                }
            }

            if (SelectedShapes.Count > 1) ga.MultipleChanged();
        }

        #endregion Move action - simplified transformation

        #region Transformation action on selected objects

        internal void PreviewTransformSelection(List<Rect> refList, Rect r, Matrix transformMatrix) {
            for (int i = 0; i < SelectedShapes.Count; i++) {
                if (SelectedShapes[i] != null) {
                    var m = transformMatrix;
                    // relative transform against centerpoint of selection rectangle
                    m.OffsetX += (transformMatrix.M11 - 1) * (refList[i].Left - r.Left);
                    m.OffsetY += (transformMatrix.M22 - 1) * (refList[i].Top - r.Top);
                    //SelectedShapes[i].RenderTransform = new MatrixTransform {Matrix = m};

                    Point topleft = m.Transform(new Point());
                    Point bottomright = m.Transform(new Point(refList[i].Width, refList[i].Height));

                    SelectedShapes[i].Width = Math.Abs(bottomright.X - topleft.X);
                    SelectedShapes[i].Height = Math.Abs(bottomright.Y - topleft.Y);
                    // in case of flipping object, correct the left and top coordinate
                    if (m.M11 < 0) {
                        topleft.X = topleft.X - SelectedShapes[i].Width;
                    }
                    if (m.M22 < 0) {
                        topleft.Y = topleft.Y - SelectedShapes[i].Height;
                    }

                    SelectedShapes[i].Left = refList[i].Left + topleft.X;
                    SelectedShapes[i].Top = refList[i].Top + topleft.Y;
                } else {
                    throw new NullReferenceException("Null object in SelectedShapes");
                }
            }
        }

        internal void TransformSelection() {
            for (int i = 0; i < SelectedShapes.Count; i++) {
                if (SelectedShapes[i] != null) {
                    SelectedShapes[i].ga.StartUpdate();

                    SelectedShapes[i].ga.Width = SelectedShapes[i].Width;
                    SelectedShapes[i].ga.Height = SelectedShapes[i].Height;
                    SelectedShapes[i].ga.Left = SelectedShapes[i].Left;
                    SelectedShapes[i].ga.Top = SelectedShapes[i].Top;

                    SelectedShapes[i].ga.FinishUpdate();
                } else {
                    throw new NullReferenceException("Null object in SelectedShapes");
                }
            }

            if (SelectedShapes.Count > 1) {
                ga.MultipleChanged();
            }
        }

        #endregion Transformation action on selected objects

        #region Z-order manipulation

        internal bool zflag;
        internal void UpSelection() {
            zflag = true;
            ga.BringForward(ToList);
        }

        internal void DownSelection() {
            zflag = true;
            ga.SendBackward(ToList);
        }

        internal void TopSelection() {
            zflag = true;
            ga.BringToFront(ToList);
        }

        internal void BottomSelection() {
            zflag = true;
            ga.SendToBack(ToList);
        }

        #endregion Z-order manipulation

        /// <summary>
        /// Function for deleting selection from canvas.
        /// </summary>
        internal void DeleteSelection() {
            ga.StartUpdate();
            foreach (GraphicsComponent item in SelectedShapes) {
                // first, remove from graphic view
                canvas.Children.Remove(item);
                // second, remove from AST
                ga.RemovePrimitive(item.ga);
            }
            ClearSelection();
            ga.FinishUpdate();
            // now the object should be garbage collected
        }
        
        private void OnSelectionChanged() {
            switch (SelectedShapes.Count) {
                case 0:
                    // TODO this.parentLayer.Deselect();
                    break;
                case 1:
                    SelectedShapes[0].ga.Select();
                    break;
                default:
                    ga.MultipleSelect(ToList);
                    break;
            }

            if (SelectionChanged != null) {
                SelectionChanged(this, null);
            }
        }
        
        internal List<IGraphicsItem> ToList {
            get { return SelectedShapes.Select(i => i.ga).ToList(); }
        }

    }
}
